package com.stark.commons.spring.web.support.feign;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.stark.commons.lang.util.Utils;
import feign.QueryMapEncoder;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 使用 JSON 序列化 SpringQueryMap 对象。
 * <p>解决以下问题：
 * <ul>
 * <li>POJO 继承父类的属性无法序列化；</li>
 * <li>日期、数字等序列化后格式错误。</li>
 * </ul>
 * @author Ben
 * @since 1.0.0
 * @version 1.0.0
 */
public class JsonQueryMapEncoder implements QueryMapEncoder {

	@SuppressWarnings("unchecked")
	@Override
	public Map<String, Object> encode(Object object) {
		if (object == null) {
			return Maps.newHashMap();
		}
		
		if (object instanceof Map) {
			return (Map<String, Object>) object;
		}
		
		Map<String, Object> map = new HashMap<>();
		beanToMap(map, null, object);
		return map;
	}
	
	/**
	 * 实体类属性扁平化。
	 * @param map 扁平化后的属性集合。
	 * @param prefix 属性前缀。
	 * @param bean 表单类。
	 */
	@SuppressWarnings("unchecked")
	private static void beanToMap(Map<String, Object> map, String prefix, Object bean) {
		Map<String, Object> propertyMap;
		try {
			ObjectMapper objectMapper = new ObjectMapper();
			String jsonStr = objectMapper.writeValueAsString(bean);
			if (StringUtils.isBlank(jsonStr)) {
				return;
			}
			propertyMap = objectMapper.readValue(jsonStr, Map.class);
		} catch (JsonProcessingException e) {
			throw new RuntimeException("POJO 转 JSON 失败: pojo=" + bean, e);
		}
		
		List<Field> fields = FieldUtils.getAllFieldsList(bean.getClass());
		Object property;
		for (Field field : fields) {
			if (!propertyMap.containsKey(field.getName())) {
				continue;
			}

			try {
				property = FieldUtils.readDeclaredField(bean, field.getName(), true);
			} catch (Exception e) {
				throw new RuntimeException("读取属性值失败", e);
			}
			if (property != null) {
				if (Utils.isNativeClass(field.getType()) || Enum.class.isAssignableFrom(field.getType())) {
					map.put((StringUtils.isBlank(prefix) ? "" : prefix + ".") + field.getName(), propertyMap.get(field.getName()));
				} else {
					beanToMap(map, (StringUtils.isBlank(prefix) ? "" : prefix + ".") + field.getName(), property);
				}
			}
		}
	}
	
}
